package com.leyou.gateway.filters;

import com.leyou.common.constants.RedisConstants;
import com.leyou.common.constants.UserTokenConstants;
import com.leyou.common.entity.Payload;
import com.leyou.common.entity.UserInfo;
import com.leyou.common.utils.JwtUtils;
import com.leyou.gateway.config.FilterProperties;
import com.leyou.gateway.config.JwtProperties;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.security.SignatureException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;


@Slf4j
@Component
public class LoginFilter implements GlobalFilter, Ordered {

    private final JwtProperties jwtProp;
    private final FilterProperties filterProp;
    private final StringRedisTemplate redisTemplate;

    public LoginFilter(JwtProperties jwtProp, FilterProperties filterProp, StringRedisTemplate redisTemplate) {
        this.jwtProp = jwtProp;
        this.filterProp = filterProp;
        this.redisTemplate = redisTemplate;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1.获取Request对象
        ServerHttpRequest request = exchange.getRequest();
        // 2.获取cookie中的token
        MultiValueMap<String, HttpCookie> cookies = request.getCookies();
        List<HttpCookie> cookieList = cookies.get(UserTokenConstants.COOKIE_NAME);
        try {
            if (CollectionUtils.isEmpty(cookieList)) {
                // 没有token，拦截请求
                throw new SignatureException("未登录！");
            }
            String token = cookieList.get(0).getValue();
            // 3.通过JWT对token进行解析
            Payload<UserInfo> payload = JwtUtils.getInfoFromToken(token, jwtProp.getPublicKey(), UserInfo.class);
            // 3.1.获取用户信息
            UserInfo userInfo = payload.getUserInfo();
            // 3.2.获取redis中的JTI
            String key = RedisConstants.JTI_KEY_PREFIX + userInfo.getId();
            String cacheJTI = redisTemplate.opsForValue().get(key);
            // 3.3.比较当前token的jti和redis中的JTI
            if (!StringUtils.equals(cacheJTI, payload.getId())) {
                // 无效的token，返回400
                throw new SignatureException("未登录或者登录已经过期");
            }
            // 4.刷新token有效期
            redisTemplate.expire(key, RedisConstants.TOKEN_EXPIRE_MINUTES, TimeUnit.MINUTES);
            // TODO 权限判断
            log.info("用户{}正在访问资源：{}", userInfo.getUsername(), request.getPath());
            // 放行
            return chain.filter(exchange);
        } catch (ExpiredJwtException | SignatureException e) {
            // 没有登录，判断当前请求是否是白名单请求
            if (isAllowRequest(request)) {
                // 如果是，放行
                return chain.filter(exchange);
            }
            log.error("用户非法访问资源：{}", request.getPath());
            //  - 解析不通过，返回401
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        } catch (Exception e) {
            log.error("服务解析用户信息失败", e);
            // 内部异常，返回500
            exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
            return exchange.getResponse().setComplete();
        }
    }

    @Override
    public int getOrder() {
        // 最高优先级
        return Ordered.HIGHEST_PRECEDENCE;
    }

    /**
     * 判断一个请求是否是白名单中的请求
     *
     * @param request 请求对象
     * @return true：是白名单请求； false：不是白名单请求
     */
    private boolean isAllowRequest(ServerHttpRequest request) {
        // 1.获取当前请求的path和method
        String path = request.getPath().toString();
        String method = request.getMethodValue();
        // 2.遍历白名单
        Map<String, Set<String>> allowRequests = filterProp.getAllowRequests();
        for (Map.Entry<String, Set<String>> entry : allowRequests.entrySet()) {
            // 3.获取允许的path和允许的methods
            String allowPathPrefix = entry.getKey();
            Set<String> allowMethods = entry.getValue();
            // 4.判断是否允许
            if (StringUtils.startsWith(path, allowPathPrefix) && allowMethods.contains(method)) {
                // 是许可的路径，返回true
                return true;
            }
        }
        // 不是白名单请求，返回false
        return false;
    }
}